<template>
  <div class="cropper-bg-board w-600 h-400">
    <img
      v-show="isReady"
      ref="imgElRef"
      :src="src"
      :alt="alt"
      :crossorigin="crossorigin"
      :style="imageStyle"
      :class="imageClass || 'wh-full'"
    />
  </div>
</template>

<script lang="ts" setup>
import Cropper from "cropperjs";
import "cropperjs/dist/cropper.min.css";
import { useDebounceFn } from "@vueuse/core";
import { Props, Emits } from "./types";

const defaultOptions: Cropper.Options = {
  aspectRatio: 1,
  zoomable: true,
  zoomOnTouch: true,
  zoomOnWheel: true,
  cropBoxMovable: true,
  cropBoxResizable: true,
  toggleDragModeOnDblclick: true,
  autoCrop: true,
  background: true,
  highlight: true,
  center: true,
  responsive: true,
  restore: true,
  checkCrossOrigin: true,
  checkOrientation: true,
  scalable: true,
  modal: true,
  guides: true,
  movable: true,
  rotatable: true,
};

const props = withDefaults(defineProps<Props>(), {
  realTimePreview: true,
  options: () => ({}),
});
const emits = defineEmits<Emits>();

const imgElRef = ref<HTMLImageElement>();
const cropper = ref<Cropper>();
const isReady = ref(false);

// Real-time display preview
const realTimeCroppered = () => {
  props.realTimePreview && croppered();
};

const debounceRealTimeCroppered = useDebounceFn(realTimeCroppered, 80);

// event: return base64 and width and height information after cropping
const croppered = () => {
  if (!cropper.value) {
    return;
  }
  let imgInfo = cropper.value.getData();
  const canvas = props.circled
    ? getRoundedCanvas()
    : cropper.value.getCroppedCanvas();
  canvas.toBlob((blob) => {
    if (!blob) return;

    let fileReader: FileReader = new FileReader();
    fileReader.readAsDataURL(blob);
    fileReader.onloadend = (e) => {
      emits("cropend", {
        imgBase64: e.target?.result ?? "",
        imgInfo,
      });
    };
    fileReader.onerror = () => {
      emits("cropendError");
    };
  }, "image/png");
};

// Get a circular picture canvas
const getRoundedCanvas = () => {
  const sourceCanvas = cropper.value!.getCroppedCanvas();
  const canvas = document.createElement("canvas");
  const context = canvas.getContext("2d")!;
  const width = sourceCanvas.width;
  const height = sourceCanvas.height;
  canvas.width = width;
  canvas.height = height;
  context.imageSmoothingEnabled = true;
  context.drawImage(sourceCanvas, 0, 0, width, height);
  context.globalCompositeOperation = "destination-in";
  context.beginPath();
  context.arc(
    width / 2,
    height / 2,
    Math.min(width, height) / 2,
    0,
    2 * Math.PI,
    true
  );
  context.fill();
  return canvas;
};

const init = async () => {
  cropper.value = new Cropper(imgElRef.value!, {
    ...defaultOptions,
    ready: () => {
      isReady.value = true;
      realTimeCroppered();
      emits("ready", cropper.value!);
    },
    crop() {
      debounceRealTimeCroppered();
    },
    zoom() {
      debounceRealTimeCroppered();
    },
    cropmove() {
      debounceRealTimeCroppered();
    },
    ...props.options,
  });
};

watch(
  () => props.src,
  async () => {
    cropper.value?.destroy();
    await nextTick();
    init();
  }
);

onUnmounted(() => {
  cropper.value?.destroy();
});
</script>

<style scoped>
.cropper-bg-board {
  background-color: #fff;
  background-image: linear-gradient(45deg, #ccc 25%, transparent 0),
    linear-gradient(45deg, transparent 75%, #ccc 0),
    linear-gradient(45deg, #ccc 25%, transparent 0),
    linear-gradient(45deg, transparent 75%, #ccc 0);
  background-position: 0 0, 6px 6px, 6px 6px, 12px 12px;
  background-size: 12px 12px;
}
</style>
